package blingking.jfinal.orm.tx;

import blingking.jfinal.orm.annotation.JFinalORMTx;
import com.jfinal.kit.LogKit;
import com.jfinal.plugin.activerecord.ActiveRecordException;
import com.jfinal.plugin.activerecord.Config;
import com.jfinal.plugin.activerecord.DbKit;
import com.jfinal.plugin.activerecord.NestedTransactionHelpException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author King
 * @description
 * @Date 2020/3/22
 */
public class NativeTx {

    public static Object execAOPTx(ProceedingJoinPoint pjp) throws SQLException {
        Object retVal = null;
        Set<Config> configs = new HashSet<>();
        Map<String, Boolean> configAutoComitMap = new HashMap<>();
        try {
            getTxDataSourcesConfigs(pjp,configs);
            initConfigs(configs,configAutoComitMap);
            retVal = pjp.proceed();
            commitConnections(configs);
        } catch (NestedTransactionHelpException e) {
            rollbackConnections(configs);
            LogKit.logNothing(e);
        } catch (Throwable t) {
            rollbackConnections(configs);
            throw t instanceof RuntimeException ? (RuntimeException) t : new ActiveRecordException(t);
        } finally {
            finallyExec(configs,configAutoComitMap);
        }
        return retVal;
    }

    private static Set<Config> getTxDataSourcesConfigs(ProceedingJoinPoint pjp,Set<Config> configs) {
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        Method method = ms.getMethod();
        JFinalORMTx jFinalORMTx = method.getAnnotation(JFinalORMTx.class);
        String[] dataSources = jFinalORMTx.dataSources();
        if (dataSources != null && dataSources.length > 0) {
            for (String dataSource : dataSources) {
                configs.add(DbKit.getConfig(dataSource));
            }
        } else {
            configs.add(DbKit.getConfig());
        }
        return configs;

    }

    private static void initConfigs(Set<Config> configs,Map<String, Boolean> configAutoComitMap) throws SQLException {
        for (Config config : configs) {
            Connection conn = config.getConnection();
            if (conn.getTransactionIsolation() < getTransactionLevel(config)) {
                conn.setTransactionIsolation(getTransactionLevel(config));
            }
            boolean autoCommit = conn.getAutoCommit();
            configAutoComitMap.put(config.getName(), autoCommit);
            config.setThreadLocalConnection(conn);
            conn.setTransactionIsolation(getTransactionLevel(config));
            conn.setAutoCommit(false);
        }
    }

    private static void commitConnections(Set<Config> configs) throws SQLException {
        for (Config config : configs) {
            Connection conn = config.getConnection();
            conn.commit();
        }
    }

    private static void rollbackConnections(Set<Config> configs) throws SQLException {
        for (Config config : configs) {
            Connection conn = config.getConnection();
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (Exception e1) {
                    LogKit.error(e1.getMessage(), e1);
                }
            }
        }
    }

    private static void finallyExec(Set<Config> configs,Map<String, Boolean> configAutoComitMap) {
        for (Config config : configs) {
            try {
                Connection conn = config.getConnection();
                if (conn != null) {
                    Boolean autoCommit = configAutoComitMap.get(config.getName());
                    if (autoCommit != null)
                        conn.setAutoCommit(autoCommit);
                    conn.close();
                }
            } catch (Throwable t) {
                LogKit.error(t.getMessage(), t);
            } finally {
                // prevent memory leak
                config.removeThreadLocalConnection();
            }
        }
    }

    /**
     * 获取配置的事务级别
     *
     * @param config
     * @return
     */
    private static int getTransactionLevel(Config config) {
        return config.getTransactionLevel();
    }
}
